package org.eerbaar.parser.core.def.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eerbaar.parser.core.Grammar;
import org.eerbaar.parser.core.def.NonTerminal;
import org.eerbaar.parser.core.def.NonTerminalProduction;
import org.eerbaar.parser.core.def.Production;
import org.eerbaar.parser.core.def.Symbol;
import org.eerbaar.parser.core.def.Terminal;
import org.eerbaar.parser.core.def.TerminalProduction;

public class DefaultGrammar
    implements Grammar {

    private final List<Terminal> terminals;
    private final List<NonTerminal> nonTerminals;
    private final List<NonTerminalProduction> nonTerminalProductions;
    private final List<TerminalProduction> terminalProductions;

    private final Map<NonTerminal, List<Production>> lhsIndex;
    private final Map<Symbol, List<Production>> rhsIndex;

    public DefaultGrammar() {
        terminals = new ArrayList<Terminal>();
        nonTerminals = new ArrayList<NonTerminal>();
        nonTerminalProductions = new ArrayList<NonTerminalProduction>();
        terminalProductions = new ArrayList<TerminalProduction>();
        lhsIndex = new HashMap<NonTerminal, List<Production>>();
        rhsIndex = new HashMap<Symbol, List<Production>>();
    }

    @Override
    public void addNonTerminalProduction(Symbol lhs, Symbol[] rhs) {
        NonTerminalProduction production = new DefaultNonTerminalProduction(
                new DefaultNonTerminal(lhs.getName()), rhs);

        addProduction(production);
    }

    private void addProduction(Production production) {
        // add
        if (production instanceof NonTerminalProduction) {
            nonTerminalProductions.add((NonTerminalProduction) production);
        } else if (production instanceof TerminalProduction) {
            terminalProductions.add((TerminalProduction) production);
        }

        // index on left-hand side symbol
        NonTerminal lhs = production.getLHS();
        if (!lhsIndex.containsKey(lhs)) {
            // add key and create empty container for index
            lhsIndex.put(lhs, new ArrayList());
            // index symbol if new non-terminal
            if (!nonTerminals.contains(lhs)) {
                nonTerminals.add(lhs);
            }
        }
        (lhsIndex.get(lhs)).add(production);

        // index on right-hand side
        Symbol[] rhs = production.getRHS();
        for (Symbol sym : rhs) {
            if (!rhsIndex.containsKey(sym)) {
                // add key and create empty container for index
                rhsIndex.put(sym, new ArrayList());
                // index symbol if new terminal
                if (sym instanceof Terminal && !terminals.contains(sym)) {
                    terminals.add((Terminal) sym);
                }
            }
            (rhsIndex.get(sym)).add(production);
        }
    }

    @Override
    public void addTerminalProduction(Symbol lhs, Symbol rhs) {
        // create a production
        TerminalProduction production = new DefaultTerminalProduction(
                new DefaultNonTerminal(lhs.getName()), new Symbol[] { rhs });

        // add
        addProduction(production);
    }

    @Override
    public NonTerminalProduction[] getNonTerminalProductions() {
        return nonTerminalProductions.toArray(new NonTerminalProduction[] {});
    }

    @Override
    public NonTerminal[] getNonTerminals() {
        return nonTerminals.toArray(new NonTerminal[] {});
    }

    @Override
    public TerminalProduction[] getTerminalProductions() {
        return terminalProductions.toArray(new TerminalProduction[] {});
    }

    @Override
    public String toString() {

        String s = "";
        s += "\nNon-terminal productions:\n";
        for (NonTerminalProduction nt : getNonTerminalProductions()) {
            s += nt + "\n";
        }

        s += "\nTerminal productions:\n";
        for (TerminalProduction t : getTerminalProductions()) {
            s += t + "\n";
        }

        return s;
    }

}
